require 'singleton'

# {include:file:README.md}
module Annotated

  # @private
  module Helpers

    private

    def puts_message msg
      puts msg if ENV['ANN_CLI']
    end

    def print_message msg
      print msg if ENV['ANN_CLI']
    end

    protected

    def debug?
      @debug ||= ENV.has_key?('ANN_DEBUG')
    end

  end

  class Models

    # @private
    class Annotator

      include Helpers

      def initialize model_const, model_file_full_path
        @model, @path = model_const, model_file_full_path
        @annotatable = true
      end

      attr_reader :path, :model

      def annotatable? # I'm not sure is this a right word...
        make_annotation!
        @annotatable
      end

      def annotation
        make_annotation!
      end

      def add_regexp
        /\z/
      end

      def remove_regexp
        border = '# '+STRONG_ANNOTATION
        border.gsub!(' ', '\s')
        border.gsub!('#', '\#')
        %r{\n(\#--\n)?#{border}.+#{border}.*\n?(\#++)?}m
      end

      private

      def make_annotation!
        return @annotatable=false if @model.abstract_class?
        return @annotation if defined?(@annotation)

        # TODO add exception handling inside this method to notify about broken models
        table_name = @model.table_name.to_s
        annotation_header = <<annheader
#--
# #{ANNOTATION % 'below'}
#++
annheader
        annotation_footer = <<annfooter

#--
# #{ANNOTATION % 'above'}
#++
annfooter
        annotation_table = []

        # append table name:
        annotation_table << "# Table name: #{table_name}"
        annotation_table << '#'
        column_with_maximum_name_length = @model.columns.map(&:name).max_by { |c| c.length }.length

        @model.columns.each do |column|
          column_limit = if column.type==:decimal && column.precision
                           "(#{[column.precision, column.scale].join(',')})"
                         else
                           column.limit ? "(#{column.limit})" : ''
                         end

          other_column_attrs = []
          other_column_attrs << NOT_NULL unless column.null
          other_column_attrs << DEFAULT % column.default if column.default

          annotation_table << sprintf(
            %Q|# %s %-#{column_with_maximum_name_length+1}s:%-16s%s|,
            column.primary && PRIMARY_KEY_MARK || ' ',
            column.name,
            column.type.to_s+column_limit,
            other_column_attrs.join(',')
          ).rstrip
        end

        # annotating indexes if present
        annotation_index = []
        if (indexes = ActiveRecord::Base.connection.indexes(table_name)) and not indexes.empty?
          annotation_index << "\n#"
          annotation_index << "#  Indexes:"
          maximum_index_name_length = indexes.map(&:name).max_by { |c| c.length }.length
          indexes.each do |index|
            # todo add other index fields
            annotation_index << sprintf(
              "#   %-#{maximum_index_name_length}s  %s  %s",
              index.name,
              index.columns.join(','),
              index.unique ? 'unique' : ''
            ).rstrip
          end
        end

        @annotation = annotation_header + annotation_table.join("\n") + annotation_index.join("\n") + annotation_footer
      end

    end # Annotator

    include Singleton
    include Helpers

    STRONG_ANNOTATION = %q|generated by 'annotated-rails' gem|
    ANNOTATION        = %Q|#{STRONG_ANNOTATION}, please do not remove this line and content %s, instead use `bundle exec annotate-rails -d` command|
    NOT_NULL          = 'not null'
    PRIMARY_KEY_MARK  = '*'
    DEFAULT           = 'default %s'

    def annotate! options = {}
      print_message "Annotating: "

      annotators.each do |annotator|
        next unless annotator.annotatable?

        print_message annotator.model.to_s+' '

        content = File.read(annotator.path).gsub(annotator.remove_regexp, "\n")
        content.gsub!(annotator.add_regexp, annotator.annotation)

        write_file annotator.path, content
      end

      puts_message "\n"
    end

    def denotate!
      annotators.each do |annotator|
        content = File.read(annotator.path).gsub(annotator.remove_regexp, "\n")
        write_file annotator.path, content
      end
    end

    private

    def annotators
      return @annotators if defined?(@annotators)
      @annotators = []

      begin
        Dir[::Rails.root.join('app/models/','**/*.rb').to_s].each do |module_path|
          require module_path
          @annotators << Annotator.new(ActiveRecord::Base.descendants.last,module_path) # "last".method here, todo a better way for constants and file path's handling
        end
      rescue Exception => e
        #puts e.inspect if debug?
      end

      @annotators # make sure we return annotators
    end

    protected

    def write_file destination, content
      File.open(destination, 'wb') { |file| file.write(content) }
    end

  end
end
